#include<iostream>
#include<mutex>
using namespace std;

//智能指针
//1.在对象构造的时候获取资源，在对象析构的时候释放资源，通过对象的生命周期来管理资源
//2.类中对*和->进行重载，让对象具有指针的行为
//3.解决智能指针的拷贝问题的不同，从而衍生出了不同版本的智能指针
namespace Mango
{
//auto_ptr:采用管理权转移的方式解决智能指针的拷贝问题，一个资源只能被一个对象所管理
template<class T>
class auto_ptr
{
public:
  auto_ptr(T* ptr = nullptr) :_ptr(ptr)
  {}
  //拷贝构造函数：将传入的对象的资源交由当前对象进行管理，传入对象不再管理该资源
  auto_ptr(auto_ptr<T>& sp) :_ptr(sp._ptr) //这里不传const是传了就报错? 未解
  {
    sp._ptr = nullptr;
  }
  //赋值重载函数：现将当前对象管理的资源释放，然后将传入的对象的资源交由当前对象进行管理，传入对象不再管理该资源
  auto_ptr<T>& operator=(const auto_ptr<T>& sp)
  {
    if(this != &sp)
    {
      if(_ptr)
      {
        delete _ptr;
      }
      _ptr = sp._ptr;
      sp._ptr = nullptr;
    }
  }
  //像指针一样使用
  T& operator*()
  {
    return *_ptr;
  }
  T* operator->()
  {
    return _ptr;
  }
  ~auto_ptr()
  {
    if(_ptr)
    {
      delete _ptr;
      _ptr = nullptr;
    }
  }
private:
  T* _ptr;//智能指针所管理的资源
};

//unique_ptr:防拷贝，一个资源只能被一个对象所管理
template<class T>
class unique_ptr
{
public:
  unique_ptr(T* ptr = nullptr)  :_ptr(ptr)
  {}
  ~unique_ptr()
  {
    if(_ptr)
    {
      delete _ptr;
      _ptr = nullptr;
    }
  }
private:
  //C++98的方式:将拷贝构造函数和赋值重载函数声明为私有 并且只声明不实现
  //unique_ptr(const unique_ptr<T>&) {}
  //unique_ptr& operator=(const unique_ptr<T>&) {}
  
  //C++11的方式:在函数后面加上=delete修饰，让编译器不要生成该函数的默认版本
  unique_ptr(const unique_ptr<T>&)  = delete;
  unique_ptr& operator=(const unique_ptr<T>&) = delete;
private:
  T* _ptr;
};

//shared_ptr:通过共享引用计数的方式,让多个对象管理同一个资源
//因为对变量的++和--不是原子的,所以需要使用一把锁对引用计数进行保护
//关于引用计数和锁的存储位置:需要在堆区开辟空间
//我们希望的是：一个资源配套一个引用计数和一把锁 
//若是普通成员变量:那么每一个对象都有自己的引用计数和锁，不符合我们的要求
//若是静态成员变量:因为静态成员变量是类对象共享的，所以会导致管理同一个资源和管理不同资源的对象使用的都是同一个引用计数和同一把锁
template<class T>
class shared_ptr
{
public:
  //构造一个对象的时候,只有该对象管理该资源,所以引用计数为1,同时要为该资源配套一把锁
  shared_ptr(const T* ptr = nullptr)
    :_ptr(ptr),_pmtx(new mutex),_pRefCount(new int(1))
  {}
  void AddRef()
  {
    _pmtx->lock();
    (*_pRefCount)++;
    _pmtx->unlock();
  }
  void ReleaseRef()
  {
    _pmtx->lock();
    bool flag = false;
    //如果当前对象是最后一个管理该资源的对象,那么该对象还需要把该资源释放了
    //注意：由于引用计数和锁对象都是在堆区开辟的,所以也需要释放对应的空间
    //由于还需要解锁,所以不能提前释放锁对象,这里是借助了flag变量来判断是否需要释放锁空间
    if( (--(*_pRefCount) ) == 0)
    {
        delete _ptr; 
        delete _pRefCount;
        flag = true; 
    }
    _pmtx->unlock();
    if(flag)
    {
      delete _pmtx;
    }
  }
  ~shared_ptr()
  {
    ReleaseRef();
  }
  shared_ptr(const shared_ptr<T>& s)
    :_ptr(s._ptr),_pmtx(s._pmtx),_pRefCount(s._pRefCount)
  {
    //当前对象和传入对象一起管理其所管理的资源,并且要把锁和引用计数也交给当前对象,引用计数++
    AddRef();
  }
  shared_ptr<T>& operator=(const shared_ptr<T>& s)
  {
    //为什么不是判断二者是否是同一个对象呢?
    //因为如果s1和s2管理的是同一个资源,针对相同对象赋值的时候可以避免进入if内部
    //但是如果是s1赋值给s2或者s2赋值给s1,那么就不能避免了,此时需要减少该资源对应的引用计数,然后又增加引用计数,多此一举！
    //为了提高效率 ：判断当前对象和传入对象管理的是否是同一个资源
    if(_ptr != s._ptr)
    {
      ReleaseRef(); //减少当前对象管理的资源的引用计数
      //和传入对象管理一个资源,然后增加引用计数
      _ptr = s._ptr;
      _pmtx = s._pmtx;
      _pRefCount = s._pRefCount;
      AddRef();
    }
    return *this;
  }
  int use_count() const 
  {
     return *_pRefCount;
  }
  T* get() const 
  {
    return _ptr;
  }
private:
  T* _ptr;
  mutex* _pmtx;
  int* _pRefCount;
};

//weak_ptr的提出是为了通过不增加/减少引用计数的方式解决shared_ptr的循环引用问题
//weak_ptr本身并不符合RAII 没有接收一个原生指针的构造函数
//支持:无参构造,利用shared_ptr拷贝构造/拷贝赋值对象
template<class T>
class weak_ptr
{
public:
  weak_ptr()
      :_ptr(nullptr)
  {}
  weak_ptr(const shared_ptr<T>& s)
    :_ptr(s.get())
  {}
  weak_ptr<T>& operator=(const shared_ptr<T>& s)
  {
    _ptr = s.get();
    return *this;
  }
  T* operator->()
  {
    return _ptr;
  }
  T& operator*()
  {
    return *_ptr;
  }
  T* get() const 
  {
    return _ptr;
  }
private:
  T* _ptr;
};
}
